#include <sys/uio.h>

#include "soh.h"
#include "html.h"



/******************************************
This file contains functions for generating 
HTML pages and handling AJAX requests.
******************************************/

/*
" -> &#34;
& -> &#38;
' -> &#39;
< -> &#60;
> -> &#62;
\ -> &#92;
*/

static int htmlSpecialChars( const char* str, char* buf, int bufSize ){
	
	int i;
	int res;
	int offset;
	
	offset = 0;
	
	for( i = 0; offset < bufSize; i++ ){
		
		if( str[i] != 34 && str[i] != 38 && str[i] != 39 && str[i] != 60 && str[i] != 62 && str[i] != 92 ){
			buf[offset] = str[i];
			
			if( !str[i] ) return offset;
			
			offset++;
			continue;
		}
		
		if( (offset + 6) >= bufSize ) return -2;
		
		res = sprintf( buf + offset, "&#%i;", str[i] );
		if( res != 5 ){
			perror( "htmlSpecialChars sprintf" );
			return -2;
		}
		offset += 5;
	}
	
	return -2;
}

int shareBrowserHtml( struct instance* this ){
	
	int i;
	int  res;
	int  contentLength;
	char sizeStr[32];
	
	int  headersLength;
	char headers[1024];
	
	int  pageLength;
	char page[65536];
	
	const char* pagePart1;
	const char* pagePart2;
	const char* pagePart3;
	size_t      pagePart1Length;
	size_t      pagePart2Length;
	size_t      pagePart3Length;
	
	int  jsconfigLength;
	char jsconfig[4096];
	
	char tmpbuf[4096];
	
	unsigned char hasFile;
	unsigned char hasRaw;
	unsigned char hasTar;
	
	struct iovec fullPage[6];
	ssize_t fullPageLen;
	
	//init some vars
	contentLength = 0;
	pageLength = 0;
	jsconfigLength = 0;
	hasFile = 0x00;
	hasRaw = 0x00;
	hasTar = 0x00;
	fullPageLen = 0;
	
	pagePart1 = _binary_page_part1_html_start;
	pagePart2 = _binary_page_part2_html_start;
	pagePart3 = _binary_page_part3_html_start;
	pagePart1Length = _binary_page_part1_html_end - _binary_page_part1_html_start;
	pagePart2Length = _binary_page_part2_html_end - _binary_page_part2_html_start;
	pagePart3Length = _binary_page_part3_html_end - _binary_page_part3_html_start;
	
	/*********   avaliable utils   *********/
	
	//TODO: use for()
	
	//check file avaliable
	for( i = 0; this->progs[i].name; i++ ){
		if( !this->progs[i].path ) continue;
		if( strcmp(this->progs[i].alias, "file") ){
			hasFile = 0xff;
			break;
		}
	}
	
	//check raw avaliable
	for( i = 0; this->progs[i].name; i++ ){
		if( !this->progs[i].path ) continue;
		if( strcmp(this->progs[i].alias, "raw") ){
			hasRaw = 0xff;
			break;
		}
	}
	
	//check tar avaliable
	for( i = 0; this->progs[i].name; i++ ){
		if( !this->progs[i].path ) continue;
		if( !strcmp(this->progs[i].alias, "tar") ){
			hasTar = 0xff;
			break;
		}
	}
	
	/*********   js config   *********/
	
	//var hasFile = true | false;
	res = sprintf( jsconfig + jsconfigLength, "var hasFile = %s;\n", hasFile ? "true" : "false" );
	if( res < 0 ) goto l_sprintf_error;
	jsconfigLength += res;
	
	//var hasRaw = true | false;
	res = sprintf( jsconfig + jsconfigLength, "var hasRaw = %s;\n", hasRaw ? "true" : "false" );
	if( res < 0 ) goto l_sprintf_error;
	jsconfigLength += res;
	
	//var hasTar = true | false;
	res = sprintf( jsconfig + jsconfigLength, "var hasTar = %s;\n", hasTar ? "true" : "false" );
	if( res < 0 ) goto l_sprintf_error;
	jsconfigLength += res;
	
	//var avaliableCompressors = [ "xz", "bz2", ..., null ];
	res = sprintf( jsconfig + jsconfigLength, "var avaliableCompressors = [ ");
	if( res < 0 ) goto l_sprintf_error;
	jsconfigLength += res;
	
	for( i = 0; this->progs[i].name; i++ ){
		if( !this->progs[i].path ) continue;
		if( this->progs[i].type == PROGDESC_TYPE_COMPRESSOR ){
			res = sprintf( jsconfig + jsconfigLength, "\"%s\", ", this->progs[i].alias );
			if( res < 0 ) goto l_sprintf_error;
			jsconfigLength += res;
		}
	}
	
	res = sprintf( jsconfig + jsconfigLength, "null ];\n" );
	if( res < 0 ) goto l_sprintf_error;
	jsconfigLength += res;
	
	/*********   root nodes   *********/
	
	//convert bytes to readable string
	readableSize( sizeStr, this->root.size );
	
	//replace html special chars in name
	res = htmlSpecialChars( this->root.desc + 1, tmpbuf, sizeof(tmpbuf) );
	if( res < 0 ) goto l_500;
	
	//generate root directory node
	if( (this->root.desc[0] & FILEDESC_TYPE_MASK) == FILEDESC_TYPE_DIR ){
		
		res = sprintf( page + pageLength, 
			"<div class=\"nodeListItem\"><input type=\"hidden\" class=\"_dirId\" value=\"%i\">"
			"<span class=\"dirName\" onClick=\"dirIn( this );\">%s</span><small class=\"nodeSize\"> (%s)</small>\n"
			"<div class=\"dirExpander\"></div><div class=\"nodeInfoBlock\">\n",
			0,//index
			tmpbuf, //name
			sizeStr //size
		);
		if( res < 0 ) goto l_sprintf_error;
		pageLength += res;
		
	//generate root file node
	}else{
		
		res = sprintf( page + pageLength,
			"<div class=\"nodeListItem\"><span class=\"fileName\">%s </span>"
			"<small class=\"nodeSize\"> (%s)</small><div class=\"nodeInfoBlock\">", 
			tmpbuf, sizeStr
		);
		if( res < 0 ) goto l_sprintf_error;
		pageLength += res;
		
		//generate download links for case if browser not support js
		if( hasRaw && (this->root.desc[0] & FILEDESC_TYPE_MASK) == FILEDESC_TYPE_FILE ){
			
			if( hasFile ){
				res = sprintf( page + pageLength, "<a class=\"nodeInfoLink\" target=\"_blank\" href=\"download/0/view\">open</a>\n" );
				if( res < 0 ) goto l_sprintf_error;
				pageLength += res;
			}
			
			res = sprintf( page + pageLength, "<a class=\"nodeInfoLink\" target=\"_blank\" href=\"download/0/\">raw</a>\n" );
			if( res < 0 ) goto l_sprintf_error;
			pageLength += res;
			
			for( i = 0; this->progs[i].name; i++ ){
				if( !this->progs[i].path ) continue;
				if( this->progs[i].type != PROGDESC_TYPE_COMPRESSOR ) continue;
				
				res = sprintf( page + pageLength, "<a class=\"nodeInfoLink\" target=\"_blank\" href=\"download/0/%s\">.%s</a>\n",
					this->progs[i].alias, this->progs[i].alias
				);
				if( res < 0 ) goto l_sprintf_error;
				pageLength += res;
			}
		}
	}
	
	//generate download links for case if browser not support js
	if( hasTar ){
		res = sprintf( page + pageLength, "<a class=\"nodeInfoLink\" target=\"_blank\" href=\"download/0/tar\">.tar</a>\n" );
		if( res < 0 ) goto l_sprintf_error;
		pageLength += res;
		
		for( i = 0; this->progs[i].name; i++ ){
			if( !this->progs[i].path ) continue;
			if( this->progs[i].type != PROGDESC_TYPE_COMPRESSOR ) continue;
			
			res = sprintf( page + pageLength, "<a class=\"nodeInfoLink\" target=\"_blank\" href=\"download/0/%s.%s\">%s.%s</a>\n",
				"tar", this->progs[i].alias, "tar", this->progs[i].alias
			);
			if( res < 0 ) goto l_sprintf_error;
			pageLength += res;
		}
	}
	
	res = sprintf( page + pageLength, "</div></div>" );
	if( res < 0 ) goto l_sprintf_error;
	pageLength +=res;
	
	
	/*********   generate header   *********/
	
	//calculate content length
	contentLength += pagePart1Length;
	contentLength += jsconfigLength;
	contentLength += pagePart2Length;
	contentLength += pageLength;
	contentLength += pagePart3Length;
	
	//create headers
	headersLength = sprintf( headers,
		"%s 200 OK\r\n"                               //this->protocol
		"Content-Type: text/html; charset=utf-8\r\n"
		"Content-Length: %i\r\n"                      //contentLength
		"\r\n",
		this->protocol, contentLength
	);
	
	/*********   send page   *********/
	
	//this is vertor write experiment
	
	//setup vector
	fullPage[0].iov_base = headers;          fullPage[0].iov_len = headersLength;
	fullPage[1].iov_base = (void*)pagePart1; fullPage[1].iov_len = pagePart1Length;
	fullPage[2].iov_base = jsconfig;         fullPage[2].iov_len = jsconfigLength;
	fullPage[3].iov_base = (void*)pagePart2; fullPage[3].iov_len = pagePart2Length;
	fullPage[4].iov_base = page;             fullPage[4].iov_len = pageLength;
	fullPage[5].iov_base = (void*)pagePart3; fullPage[5].iov_len = pagePart3Length;
	
	//calculate total len
	for( i = 0; i < 6; i++ ){
		fullPageLen += fullPage[i].iov_len;
	}
	
	//send full papge
	res = writev( this->clientSocket, fullPage, 6 );
	if( res != fullPageLen ){
		perror( "shareBrowserHtml writev" );
		return -1;
	}
	
	/*
	//send headers
	res = sendData( this->clientSocket, headers, headersLength );
	if( res ) return res;
	
	//send page part 1
	res = sendData( this->clientSocket, pagePart1, pagePart1Length );
	if( res ) return res;
	
	//send js config
	res = sendData( this->clientSocket, jsconfig, jsconfigLength );
	if( res ) return res;
	
	//send page part 2
	res = sendData( this->clientSocket, pagePart2, pagePart2Length );
	if( res ) return res;
	
	//send root nodes
	res = sendData( this->clientSocket, page, pageLength );
	if( res ) return res;
	
	//send page part 3
	res = sendData( this->clientSocket, pagePart3, pagePart3Length );
	if( res ) return res;
	*/
	
	printf( "<<< 200 OK.\n" );
	
	return 0;
	
l_sprintf_error:
	perror( "sprintf" );
	
l_500:
	return httpSendStatus( this, 500, "Internal server error." );
}

int loadNodesBackend( struct instance* this, size_t uriOffset ){
	
	int  res;
	long long int i;
	int  parseUriRes;
	int  headersLength;
	char tmpbuf[4096];
	int  pageLength;
	char page[10240];
	struct file_db* nodes;
	long long int nodeIndex;
	
	nodes = &this->root;
	
	/*********   search requested node in index   *********/
	
l_uriParseNext:
	//parse uri
	parseUriRes = parserCore( this->uri, '/', tmpbuf, sizeof(tmpbuf), &uriOffset );
	if( parseUriRes < 0 ) goto l_400;
	
	//try read int from uri part
	res = sscanf( tmpbuf, "%lli", &nodeIndex );
	if( res != 1 ) goto l_400;
	
	//search requested node in nodes tree
	for( i = 0; i != nodeIndex; i++ ){
		if( nodes[i].desc[0] & FILEDESC_LAST_FLAG ) goto l_403;
	}
	
	if( parseUriRes ){
		
		//some check before "open" node
		if( (nodes[nodeIndex].desc[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_DIR ) goto l_403;
		if( !nodes[nodeIndex].entries ) goto l_403;
		
		//"open" node
		nodes = nodes[nodeIndex].entries;
		
		i++;
		goto l_uriParseNext;
	}
	
	//set pointer to entries of requested node
	nodes = nodes[nodeIndex].entries;
	
	/*********   http headers   *********/
	
	//create headers
	headersLength = sprintf( tmpbuf,
		"%s 200 OK\r\n"                               //this->protocol
		"Content-Type: text/html; charset=utf-8\r\n"
		"\r\n",
		this->protocol
	);
	
	//send headers
	res = sendData( this->clientSocket, tmpbuf, headersLength );
	if( res ) return res;
	
	/*********   send node info   *********/
	
	//if node has not child nodes
	if( !nodes ){
		res = sendData( this->clientSocket, cnEmptyDir, sizeof(cnEmptyDir) - 1 );
		if( res ) return res;
		goto l_200;
	}
	
	//read and send info about node entries
	i = 0;
	
l_generateNextCn:
	
	//replace html special chars in name
	res = htmlSpecialChars( nodes[i].desc + 1, tmpbuf, sizeof(tmpbuf) );
	if( res < 0 ) goto l_500;
	
	//generate cn( nodeType, nodeId, nodeName, nodeSize );
	pageLength = sprintf( page, "cn(%i,%lli,\"%s\",%li);",
		(nodes[i].desc[0] & FILEDESC_TYPE_MASK), //node type
		i, //index id
		tmpbuf, //node name
		nodes[i].size //node size
	);
	
	//send node entry
	res = sendData( this->clientSocket, page, pageLength );
	if( res ) return res;
	
	//if this not last, then generate next cn function
	if( !(nodes[i].desc[0] & FILEDESC_LAST_FLAG) ){
		i++;
		goto l_generateNextCn;
	}
	
l_200:
	printf( "<<< 200 OK.\n" );
	return 0;
	
	
l_400: return httpSendStatus( this, 400, "Bad request."   );
l_403: return httpSendStatus( this, 403, "Access denied." );
l_500: return httpSendStatus( this, 500, "Internal server error." );
}


static int _htmlDownloadHandler( struct instance* this, size_t uriOffset, long long int* depth ){
	
	int res;
	int uriParseRes;
	char uriPart[32];
	long long int i;
	long long int nodeIndex;
	struct file_db* nodes;
	struct file_db* targetNode;
	struct stat statbuf;
	
	//init some vars
	nodes = &this->root;
	targetNode = NULL;
	*depth = 0;
	
l_parseUri:
	
	//parse uri
	uriParseRes = parserCore( this->uri, '/', uriPart, sizeof(uriPart), &uriOffset );
	if( uriParseRes < 0 ) goto l_400;
	
	//if this last uri part, then start archiver
	if( !uriParseRes ){
		if( !targetNode ) goto l_400;
		return unixDownloadHandler( this, targetNode, uriPart );
	}
	
	//try convert string to node index
	res = sscanf( uriPart, "%lli", &nodeIndex );
	if( res != 1 ) goto l_400;
	
	//check current node has childs
	if( !nodes ) goto l_403;
	
	//search requested node
	for( i = 0; i != nodeIndex; i++ ){
		if( nodes[i].desc[0] & FILEDESC_LAST_FLAG ) goto l_403;
	}
	
	if( targetNode ){
		
		//check type in our index is dir before open
		if( (targetNode->desc[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_DIR ) goto l_403;
		
		//check type on disk is dir before open
		res = lstat( targetNode->desc + 1, &statbuf);
		if( res < 0 || (statbuf.st_mode & S_IFMT) != S_IFDIR ) goto l_500;
		
		//cd into dir
		res = chdir( targetNode->desc + 1 );
		if( res ){
			perror( "chdir" );
			printf( "FATAL: Cannot open indexed folder: %s\n", targetNode->desc + 1 );
			goto l_500;
		}
		
		//increase depth count
		(*depth)++;
	}
	
	//set pointers
	targetNode = &nodes[nodeIndex];
	nodes = nodes[nodeIndex].entries;
	
	//parse next uri part
	goto l_parseUri;
	
l_400: return httpSendStatus( this, 400, "Bad request." );
l_403: return httpSendStatus( this, 403, "Access denied." );
l_500:
	httpSendStatus( this, 500, "Internal server error." );
	return -2;
}

int htmlDownloadHandler( struct instance* this, size_t uriOffset ){
	
	int res;
	long long int depth;
	
	depth = 0;
	
	res = _htmlDownloadHandler( this, uriOffset, &depth );
	if( res < -1 ) return res;
	
	while( depth ){
		res = chdir( "../" );
		if( res ){
			perror( "FATAL: Cannot exit from directory" );
			return -2;
		}
		depth--;
	}
	
	return res;
}
